/*
 * Galaxium Messenger
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 *
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Gdk;
using Gtk;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Protocol;

namespace Galaxium.Gui.GtkGui
{
	public partial class ContactTreeView : GalaxiumTreeView
	{
		ISession _session;
		IContactTreeManager _manager;
		
		Dictionary<IGroup, ContactTreeGroup> _groups = new Dictionary<IGroup, ContactTreeGroup> ();
		ContactTreeGroup _onlineGroup;
		ContactTreeGroup _offlineGroup;
		int _building;
		
		public IContactTreeManager Manager
		{
			get { return _manager; }
			set
			{
				_manager = value;
				_manager.Init (this, _session);
			}
		}
		
		public ContactTreeView (ISession session)
		{
			_session = session;
			
			_session.GroupAdded += SessionGroupAdded;
			_session.GroupRemoved += SessionGroupRemoved;
			_session.GroupRenamed += SessionGroupChanged;
			_session.ContactAdded += SessionContactAdded;
			_session.ContactRemoved += SessionContactRemoved;
			_session.ContactChanged += SessionContactChanged;
			_session.Account.DisplaySettingsChange += AccountDisplaySettingsChange;
			
			HeadersVisible = false;
			RulesHint = false;
			Reorderable = false;
			
			InitColumns ();
			
			Rebuild ();
		}
		
		protected virtual void InitColumns ()
		{
			TreeViewColumn col = new TreeViewColumn ();
			col.Sizing = TreeViewColumnSizing.Autosize;
			
			CellRenderer renderer = new CellRendererPixbuf ();
			col.PackStart (renderer, false);
			col.SetCellDataFunc (renderer, new TreeCellDataFunc (RenderLeftImage));
			
			renderer = new CellRendererContact (_session.Account.Protocol);
			col.PackStart (renderer, true);
			col.SetCellDataFunc (renderer, new TreeCellDataFunc (RenderText));
			
			renderer = new CellRendererPixbuf ();
			col.PackStart (renderer, false);
			col.SetCellDataFunc (renderer, new TreeCellDataFunc (RenderRightImage));
			
			AppendColumn (col);
			
			col = new TreeViewColumn ();
			col.PackStart (new ExpanderCellRenderer (), true);
			col.MaxWidth = 16;
			InsertColumn (col, 0);
			ExpanderColumn = col;
		}
		
		public void Rebuild ()
		{
			bool origSort = Sort;
			Sort = false;
			_building++;
			
			if (_onlineGroup != null)
			{
				_onlineGroup.ContactAdded -= TreeGroupContactAdded;
				_onlineGroup.ContactRemoved -= TreeGroupContactRemoved;
				_onlineGroup = null;
			}
			
			if (_offlineGroup != null)
			{
				_offlineGroup.ContactAdded -= TreeGroupContactAdded;
				_offlineGroup.ContactRemoved -= TreeGroupContactRemoved;
				_offlineGroup = null;
			}
			
			Store.Clear ();
			_groups.Clear ();
			
			bool viewByGroup = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.ViewByGroup.Name, Configuration.ContactList.ViewByGroup.Default) : _session.Account.ViewByGroup;
			bool showEmptyGroups = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.ShowEmptyGroups.Name, Configuration.ContactList.ShowEmptyGroups.Default) : _session.Account.ShowEmptyGroups;
			bool showOfflineContacts = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.ShowOfflineContacts.Name, Configuration.ContactList.ShowOfflineContacts.Default) : _session.Account.ShowOfflineContacts;
			bool groupOfflineContacts = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.GroupOfflineContacts.Name, Configuration.ContactList.GroupOfflineContacts.Default) : _session.Account.GroupOfflineContacts;
			
			if (!viewByGroup)
			{
				_onlineGroup = new ContactTreeOnlineVirtualGroup ();
				_onlineGroup.ContactAdded += TreeGroupContactAdded;
				_onlineGroup.ContactRemoved += TreeGroupContactRemoved;
				_onlineGroup.ContactChanged += TreeGroupContactChanged;
				
				if (showEmptyGroups || (_onlineGroup.Count > 0))
					_onlineGroup._ref = AddRow (_onlineGroup);
			}
			
			if ((showOfflineContacts && groupOfflineContacts) || (!viewByGroup))
			{
				_offlineGroup = new ContactTreeOfflineVirtualGroup ();
				_offlineGroup.ContactAdded += TreeGroupContactAdded;
				_offlineGroup.ContactRemoved += TreeGroupContactRemoved;
				_offlineGroup.ContactChanged += TreeGroupContactChanged;
				
				if (showEmptyGroups || (_offlineGroup.Count > 0))
					_offlineGroup._ref = AddRow (_offlineGroup);
			}
			
			if (viewByGroup)
			{
				foreach (IGroup group in _session.GroupCollection)
					AddGroup (group);
			}
			
			UpdateContacts ();
			
			_building--;
			Sort = origSort;
			
			foreach (ContactTreeGroup treeGroup in _groups.Values)
				AutoExpandGroup (treeGroup);
			
			if (_onlineGroup != null)
				AutoExpandGroup (_onlineGroup);
			
			if (_offlineGroup != null)
				AutoExpandGroup (_offlineGroup);
		}
		
		void UpdateContacts ()
		{
			UpdateContacts (_session.ContactCollection);
		}
		
		void UpdateContacts (params IContact[] contacts)
		{
			UpdateContacts ((IEnumerable<IContact>)contacts);
		}
		
		void UpdateContacts (IEnumerable<IContact> contacts)
		{
			if (_onlineGroup != null)
				_onlineGroup.UpdateContacts (contacts);
			
			if (_offlineGroup != null)
				_offlineGroup.UpdateContacts (contacts);
			
			foreach (ContactTreeGroup treeGroup in _groups.Values)
				treeGroup.UpdateContacts (contacts);
		}
		
		void SessionGroupAdded (object sender, GroupEventArgs args)
		{
			AddGroup (args.Group);
		}
		
		void SessionGroupRemoved (object sender, GroupEventArgs args)
		{
			if (!_groups.ContainsKey (args.Group))
				return;
			
			ContactTreeGroup treeGroup = _groups[args.Group];
			
			treeGroup.ContactAdded -= TreeGroupContactAdded;
			treeGroup.ContactRemoved -= TreeGroupContactRemoved;
			treeGroup.ContactChanged -= TreeGroupContactChanged;
			
			_groups.Remove (args.Group);
			RemoveRow (treeGroup._ref);
		}
		
		void SessionGroupChanged (object sender, GroupEventArgs args)
		{
			if (!_groups.ContainsKey (args.Group))
				return;
			
			RowChanged (_groups[args.Group]._ref);
		}
		
		void SessionContactAdded (object sender, ContactListEventArgs args)
		{
			UpdateContacts (args.Contact);
		}
		
		void SessionContactRemoved (object sender, ContactListEventArgs args)
		{
			UpdateContacts (args.Contact);
		}
		
		void SessionContactChanged (object sender, ContactEventArgs args)
		{
			UpdateContacts (args.Contact);
		}
		
		void AccountDisplaySettingsChange (object sender, PropertyEventArgs args)
		{
			switch (args.Property)
			{
			case "ViewByGroup":
			case "ShowEmptyGroups":
			case "GroupOfflineContacts":
				Rebuild ();
				break;
			case "ShowOfflineContacts":
				UpdateContacts ();
				Rebuild ();
				break;
			case "SortAlphabetic":
				Resort ();
				break;
			case "SortAscending":
				if ((sender as IAccount).UseDefaultListView)
					SortOrder = Configuration.ContactList.Section.GetBool (Configuration.ContactList.SortAscending.Name, Configuration.ContactList.SortAscending.Default) ? SortOrder.Ascending : SortOrder.Descending;
				else
					SortOrder = (sender as IAccount).SortAscending ? SortOrder.Ascending : SortOrder.Descending;
				break;
			default:
				AllRowsChanged ();
				break;
			}
		}
		
		void TreeGroupContactAdded (object sender, ContactEventArgs args)
		{
			ContactTreeGroup treeGroup = sender as ContactTreeGroup;
			
			if (treeGroup._ref == null)
			{
				// The group wasn't being displayed, add it to the tree
				
				_building++;
				treeGroup._ref = AddRow (treeGroup);
				_building--;
			}
			
			treeGroup.SetContactRef (args.Contact, AddRow (treeGroup._ref, args.Contact));
			
			Resort ();
			Refilter ();
			AutoExpandGroup (treeGroup);
		}
		
		void TreeGroupContactRemoved (object sender, ContactEventArgs args)
		{
			ContactTreeGroup treeGroup = sender as ContactTreeGroup;
			RemoveRow (treeGroup.GetContactRef (args.Contact));
			
			bool showEmptyGroups = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.ShowEmptyGroups.Name, Configuration.ContactList.ShowEmptyGroups.Default) : _session.Account.ShowEmptyGroups;
			
			// The count property still includes the group currently being removed
			if ((treeGroup.Count <= 1) && (!showEmptyGroups))
			{
				// Remove the group from the tree
				_building++;
				RemoveRow (treeGroup._ref);
				treeGroup._ref = null;
				_building--;
			}
		}
		
		void TreeGroupContactChanged (object sender, ContactEventArgs args)
		{
			ContactTreeGroup treeGroup = sender as ContactTreeGroup;
			RowChanged (treeGroup.GetContactRef (args.Contact));
		}
		
		void AddGroup (IGroup group)
		{
			ContactTreeGroup treeGroup = new ContactTreeRealGroup (group);
			
			if (treeGroup == null)
				return;
			
			treeGroup.ContactAdded += TreeGroupContactAdded;
			treeGroup.ContactRemoved += TreeGroupContactRemoved;
			treeGroup.ContactChanged += TreeGroupContactChanged;
			
			_groups.Add (group, treeGroup);
			
			bool showEmptyGroups = _session.Account.UseDefaultListView ? Configuration.ContactList.Section.GetBool (Configuration.ContactList.ShowEmptyGroups.Name, Configuration.ContactList.ShowEmptyGroups.Default) : _session.Account.ShowEmptyGroups;
			
			if (showEmptyGroups || (treeGroup.Count > 0))
				treeGroup._ref = AddRow (treeGroup);
			
			Resort ();
			Refilter ();
		}
		
		void AutoExpandGroup (ContactTreeGroup treeGroup)
		{
			if (treeGroup._ref == null)
				return;
			
			bool expanded = Configuration.Account.Section[_session.Account.Protocol.Name][_session.Account.UniqueIdentifier]["Groups"][treeGroup.Name].GetBool ("Expanded", true);
			
			if (expanded)
				ExpandRow (GetModelPath (treeGroup._ref.Path), true);
			else
				CollapseRow (GetModelPath (treeGroup._ref.Path));
		}
		
		void StoreExpanded (ContactTreeGroup treeGroup)
		{
			bool expanded = GetRowExpanded (GetModelPath (treeGroup._ref.Path));
			//Log.Debug ("{0} expanded={1} building={2}", treeGroup.Name, expanded, _building);
			Configuration.Account.Section[_session.Account.Protocol.Name][_session.Account.UniqueIdentifier]["Groups"][treeGroup.Name].SetBool ("Expanded", expanded);
		}
		
		protected override void OnRowExpanded (TreeIter iter, TreePath path)
		{
			base.OnRowExpanded (iter, path);
			
			if (_building > 0)
				return;
			
			ContactTreeGroup treeGroup = GetModelData (path) as ContactTreeGroup;
			
			if (treeGroup != null)
				StoreExpanded (treeGroup);
		}
		
		protected override void OnRowCollapsed (TreeIter iter, TreePath path)
		{
			base.OnRowCollapsed (iter, path);
			
			if (_building > 0)
				return;
			
			ContactTreeGroup treeGroup = GetModelData (path) as ContactTreeGroup;
			
			if (treeGroup != null)
				StoreExpanded (treeGroup);
		}
		
		public override void Refilter ()
		{
			base.Refilter ();
			
			// Re-expand the appropriate groups
			
			if (_onlineGroup != null)
				AutoExpandGroup (_onlineGroup);
			
			if (_offlineGroup != null)
				AutoExpandGroup (_offlineGroup);
			
			foreach (ContactTreeGroup treeGroup in _groups.Values)
				AutoExpandGroup (treeGroup);
		}
		
#region Rendering
		void RenderText (TreeViewColumn col, CellRenderer cell, TreeModel model, TreeIter iter)
		{
			_manager.RenderText (GetModelData (iter), cell as CellRendererContact);
		}
		
		void RenderLeftImage (TreeViewColumn col, CellRenderer cell, TreeModel model, TreeIter iter)
		{
			_manager.RenderLeftImage (GetModelData (iter), cell as CellRendererPixbuf);
		}
		
		void RenderRightImage (TreeViewColumn col, CellRenderer cell, TreeModel model, TreeIter iter)
		{
			_manager.RenderRightImage (GetModelData (iter), cell as CellRendererPixbuf);
		}
#endregion
		
#region Sorting
		protected override int Compare (object obj1, object obj2)
		{
			if (_manager == null)
				return 0;
			
			return _manager.Compare (obj1, obj2);
		}
#endregion
		
#region Filtering
		protected override bool FilterShouldDisplay (object data)
		{
			if (_manager == null)
				return true;
			
			return _manager.Visible (data, Filter, FilterCaseSensitive);
		}
#endregion
		
#region Interaction
		protected override InfoTooltip CreateTooltip (TreePath treePath, object data)
		{
			return _manager.GetTooltip (data);
		}
		
		protected override Menu CreateContextMenu (TreePath treePath, object data)
		{
			string menuPath = _manager.GetMenuExtensionPoint (data);
			
			if (string.IsNullOrEmpty (menuPath))
				return null;
			
			if (data is ContactTreeRealGroup)
				data = (data as ContactTreeRealGroup).Group;
			
			ContactTreeContext context = new ContactTreeContext (this, treePath, data);
			return MenuUtility.CreateContextMenu (menuPath, new DefaultExtensionContext (context));
		}
#endregion
		
		public IGroup GetContactGroup (TreePath contactPath)
		{
			TreePath groupPath = contactPath.Copy ();
			
			if (!groupPath.Up ())
				return null;
			
			ContactTreeRealGroup treeGroup = GetModelData (groupPath) as ContactTreeRealGroup;
			
			if (treeGroup != null)
				return treeGroup.Group;
			
			return null;
		}
	}
}
